package fr.upmc.aladyn.transactionables.saveobject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;

import fr.upmc.aladyn.transactionables.annotations.Transactionable;


/**
 * <p>Save and Restore an <code>Object</code>, field by field.</p>
 * @author Alexis Deluze & Marie-Diana Tran
 */
public class SaveRestoreObject {

	private HashMap<Integer, Object> saved_fields;
	private Object object;
	private ArrayList<Field> fields;

	/**
	 * Constructor
	 * @param o The Object to save and restore
	 */
	public SaveRestoreObject(Object o) {
		System.out.println("[SRO] Initialisation de SaveRestoreObject pour l'objet "+o.getClass().getSimpleName());
		saved_fields = new HashMap<Integer,Object>();
		object = o;
		fields = new ArrayList<Field>();
		getAllFields(fields, o.getClass());
	}
	
	/**
	 * <p>Save the Object, Field by Field.</p>
	 */
	public void save(){
		if(isTransactionnable(object)){
			System.out.println("[SRO] Sauvegarde de l'objet "+object.getClass().getSimpleName());
			try{
				for(Field f : fields){
					Boolean isAccessible = f.isAccessible();
					if(!isAccessible){
						f.setAccessible(true);
					}
					if(!Modifier.isFinal(f.getModifiers())){
						// Sauvegarde des champs
						saved_fields.put(f.hashCode(), f.get(object));
						System.out.println("\t"+f.getName()+" = "+f.get(object));
					}
					if(!isAccessible){
						f.setAccessible(false);
					}
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}

	/**
	 * <p>Restore the previously saved Object, Field by Field.</p>
	 */
	public void restore(){
		if(isTransactionnable(object)){
			System.out.println("[SRO] Restauration de l'objet "+object.getClass().getSimpleName());
			try {
				for(Field f : fields){
					Boolean isAccessible = f.isAccessible();
					if(!isAccessible){
						f.setAccessible(true);
					}
					if(!Modifier.isFinal(f.getModifiers())){
						System.out.println("\t"+f.getName()+" = "+(saved_fields.get(f.hashCode())).toString());
						f.set(object, saved_fields.get(f.hashCode()));
					}
					if(!isAccessible){
						f.setAccessible(false);
					}
				}
			} catch (IllegalArgumentException | IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	/**
	 * <p>Private method used to identify if an Object is Transactionnable</p>
	 * @param o The Object
	 * @return True if the Object o is Transactionnable
	 */
	private boolean isTransactionnable(Object o){
		Annotation[] annotations = o.getClass().getAnnotations();
		for(Annotation a : annotations){
			if(a instanceof Transactionable){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * <p>Private recursive method used to identify the Fields of a Class and all the herited Fields.</p>
	 * <p>The identified Fields are stored in the fieldToSave parameter.</p>
	 * @param fieldsToSave ArrayList containig the result.
	 * @param c The Class
	 */
	private <T> void getAllFields(ArrayList<Field> fieldsToSave, Class<T> c){
		//System.out.println("[SRO] Detection des champs de la classe "+c.getSimpleName());
		if(c != null && c != Object.class){
			for(Field f : c.getDeclaredFields()){
				if(!Modifier.isFinal(f.getModifiers())){
					//System.out.println("\t"+f.getName()+" : "+f.toString());
					fieldsToSave.add(f);
				}
			}
			getAllFields(fieldsToSave, c.getSuperclass());
		}
	}
}
